package com.mangix.http;

import com.mangix.core.MangixNode;
import com.mangix.files.MangixFiles;
import org.json.simple.JSONValue;

import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;

/**
 * Created by ZXL on 2014/6/4.
 */
@WebServlet(asyncSupported=true)
public class MangixDirectObjectServlet extends HttpServlet {

    private MangixServer server;
    private MangixFiles mangixFiles;

    public MangixDirectObjectServlet(MangixServer server) {
        this.server = server;
        this.mangixFiles = server.getNode().getFiles();
    }

    @Override
    protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String pathInfo = req.getPathInfo();
        String key;
        if (pathInfo != null) {
            key = pathInfo.substring(1);
        } else {
            resp.sendError(404);
            return;
        }
        MangixNode node = server.getNode();
        try {
            Map meta = node.getMeta(key);
            if (meta != null) {
                resp.setHeader(MangixHttpHeader.META, JSONValue.toJSONString(meta));
            } else {
                resp.sendError(404, "Key not found.");
            }
        } catch (Exception e) {
            e.printStackTrace();
            resp.sendError(500, e.getMessage());
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String pathInfo = req.getPathInfo();
        String key;
        if (pathInfo != null)
            key = pathInfo.substring(1);
        else {
            resp.sendError(404);
            return;
        }
        InputStream in = null;
        try {
            Path file = mangixFiles.getLatestFile(key);
            if (file == null || file.getFileName().toString().split("\\.")[1].equals("delete")) {
                resp.sendError(404);
            } else {
                in = Files.newInputStream(file);
                byte[] buffer = new byte[1024];
                int readLength;
                int contentLength = 0;
                OutputStream out = resp.getOutputStream();
                while ((readLength = in.read(buffer)) != -1) {
                    out.write(buffer, 0, readLength);
                    contentLength += readLength;
                }
                resp.setContentLength(contentLength);
                out.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
            resp.sendError(500);
        } finally {
            if (in != null) {
                in.close();
            }
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doPost(req, resp);
    }

    /**
     * Put an object.
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // Get the key of the object.
        String pathInfo = req.getPathInfo();
        if (pathInfo == null) {
            resp.sendError(404);
            return;
        }
        String key = pathInfo.substring(1);
        // Get the length of the object.
        int fileLength = -1;
        if (req.getHeader(MangixHttpHeader.CONTENT_LENGTH) != null) {
            fileLength = Integer.parseInt(req.getHeader(MangixHttpHeader.CONTENT_LENGTH));
        } else {
            fileLength = req.getContentLength();
        }
        if (fileLength == -1) {
            resp.sendError(400, "Unknown object length.");
            return;
        }
        // Get the version of the object.
        String version = req.getHeader(MangixHttpHeader.VERSION);
        // Get the replicas number of the object.
        String strReplicasNum = req.getHeader(MangixHttpHeader.REPLICAS_NUM);
        int replicasNum = 1;
        if (strReplicasNum != null) {
            replicasNum = Integer.parseInt(strReplicasNum);
        }
        Path filePath;
        if (version == null) {
            // This object is a new one.
            System.out.println("Put object: " + server.getNode().toString() + " " + key);
            filePath = mangixFiles.getWriteFilePath(key, (long) fileLength);
        } else {
            // This object is from other mangix nodes.
            if (version.split("\\.").length != 2 || (!version.split("\\.")[1].equals("file") && !version.split("\\.")[1].equals("delete"))) {
//                resp.setHeader("Connection", "close");
                resp.sendError(400, "Invalid version.");
                return;
            }
            Path latestLocalObject = mangixFiles.getLatestFile(key);
            if (latestLocalObject != null) {
                if (Long.parseLong(latestLocalObject.getFileName().toString().split("\\.")[0]) < Long.parseLong(version.split("\\.")[0])) {
                    filePath = Paths.get(latestLocalObject.getParent().toString(), version);
                } else {
                    resp.sendError(409, "Not latest.");
                    return;
                }
            } else {
                filePath = Paths.get(mangixFiles.getWriteFilePath(key, (long) fileLength).getParent().toString(), version);
            }
        }
        // Read the data asynchronously.
        AsyncContext context = req.startAsync();
        ServletInputStream in = req.getInputStream();
        try {
            in.setReadListener(new MangixDirectObjectReadListener(in, context, filePath, mangixFiles, replicasNum));
        } catch (IllegalStateException e) {
            // Do nothing.
        } catch (Exception e) {
            context.complete();
        }
    }

    @Override
    protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try{
            String pathInfo = req.getPathInfo();
            String key = pathInfo.substring(1);
            boolean deleteFile = Boolean.parseBoolean(req.getParameter("delete_file"));
            if (deleteFile) {
                String version = req.getHeader(MangixHttpHeader.VERSION);
                if (version == null) {
                    resp.sendError(400, "Missing header " + MangixHttpHeader.VERSION);
                    return;
                }
                if (!mangixFiles.removeDeletedObject(key, version)) {
                    resp.sendError(400, "Try remove deleted object error.");
                    return;
                }
                return;
            }
            Path obj = mangixFiles.getLatestFile(key);
            if (obj == null) {
                resp.sendError(404);
                return;
            }
            int replicasNum = mangixFiles.getReplicasNum(obj);
            if (!mangixFiles.deleteObject(key, replicasNum)) {
                resp.sendError(404);
                return;
            }
            System.out.println("Node " + server.getNode().toString() + " delete " + key + " success.");
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}
